/*
 * Copyright 2013 Agustina Martinez-Garcia

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package uk.ljmu.qudexapps.xmllib.validators;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import uk.da.dext.exceptions.QudexException;
import uk.ljmu.qudexapps.xmllib.services.QudexFedoraObjects;

public class QudexFoxmlValidatorImpl implements QudexFoxmlValidator{
	private int numOccurrencesElement = 0;
	private Elements collectionValidator;
	private static Logger log = Logger.getLogger(QudexFoxmlValidatorImpl.class);

	/**
	 * @param collectionValidator the collectionValidator to set
	 */
	public void setCollectionValidator(Elements collectionValidator) {
		this.collectionValidator = collectionValidator;
	}

	/**
	 * @return the collectionValidator
	 */
	public Elements getCollectionValidator() {
		return collectionValidator;
	}
	
	public QudexFoxmlValidatorImpl(boolean isCollectionSS) throws uk.da.dext.exceptions.QudexException {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		//Load collection-validator from XML
		InputStream is = isCollectionSS ? classLoader.getResourceAsStream("validators/collection-validator.xml")
			: classLoader.getResourceAsStream("validators/file-validator.xml");
		JAXBContext context;
		try {
			context = JAXBContext.newInstance(Elements.class);
			Unmarshaller unmarshaller = context.createUnmarshaller();
			//TODO validate against schema, note: setting schema to null will turn validator off
			unmarshaller.setSchema(null);
			this.collectionValidator = Elements.class.cast(unmarshaller.unmarshal(is));
		} catch (JAXBException e) {
			log.fatal(e.getMessage());
			throw new QudexException("Qudex validator constructor: " + e.getMessage());
		}
	}
	/**
	 * Validate Qudex metadata by namespace. Just the elements within a particular ns will be validated
	 * 
	 * @param xmlContents xml string with the contents to be validated
	 * @param valNs list of namespaces string to be validated
	 * @return true if correct metadata fields, false otherwise
	 */
	public boolean validateQudexElements(String xmlContents, List<String> valNs, QudexFedoraObjects qudexObject) {
		//AMG find the validation elements needed for the particular namespace
		Elements auxElements = this.getNamespaceElements(valNs);
		try {
			Document doc = DocumentHelper.parseText(xmlContents);
			Element rootElement = doc.getRootElement();
			boolean validation = this.validateElement(auxElements, rootElement, qudexObject);
			
			return validation;
		} catch (DocumentException e) {
			log.fatal(e.getMessage());
		}
		return false;
	}
	
	private Elements getNamespaceElements(List<String> valNs) {
		Elements auxElements = new Elements();
		auxElements.element = new ArrayList<uk.ljmu.qudexapps.xmllib.validators.Element>();
		for (String ns : valNs)
			for (uk.ljmu.qudexapps.xmllib.validators.Element elem : this.getCollectionValidator().getElement())  {
				if (elem.getNs().equalsIgnoreCase(ns))
					auxElements.element.add(elem);
			}
		return auxElements;
	}

	private boolean validateElement(Elements auxElements, Element rootElement, QudexFedoraObjects qudexObject) {
		//Check valid elements present
		for (uk.ljmu.qudexapps.xmllib.validators.Element elem : auxElements.getElement()) {
			
			//AMG Validate presence (if not optional)
			this.numberOcurrencesItem(rootElement, elem.getName(), elem.getNs());
			int multiplicity = (elem.getMultiplicity().equalsIgnoreCase("n")) ? 2 : Integer.parseInt(elem.getMultiplicity());
			if (elem.isOptional()) {
				//If present check multiplicity
				if (this.numOccurrencesElement > 1 && multiplicity == 1) {
					qudexObject.addFedoraResultsMessage("", 
							"The element <b>" + elem.getName() + "</b> can't occur more than once. Single value field.");
					return false;
				}
			} else {
				//AMG compulsory element
				if (this.numOccurrencesElement == 0) {
					qudexObject.addFedoraResultsMessage("", 
							"The element <b>" + elem.getName() + "</b> is mandatory. Please add the field to the spreadsheet.");
					return false;
				}
				// TODO Remove this hard-coded value
				if (this.numOccurrencesElement > 1 && multiplicity == 1 && !elem.getName().equalsIgnoreCase("type")) {
					qudexObject.addFedoraResultsMessage("", 
							"The element <b>" + elem.getName() + "</b> can't occur more than once. Single value field.");
					return false;
				}
			}
			this.numOccurrencesElement = 0;
		}
		return true;
	}
	//RECURSIVE SEARCH
	private void numberOcurrencesItem(Element rootElement, String elementName, String ns) {
		Iterator<?> documentIterator = rootElement.elementIterator();
		while (documentIterator.hasNext()) {
			Element elem = (Element) documentIterator.next();
			if (elem.elements().size() > 0)
				numberOcurrencesItem(elem, elementName, ns);
			else {
				if (elem.getName().equalsIgnoreCase(elementName) && 
					elem.getNamespace().getPrefix().equalsIgnoreCase(ns))
				this.numOccurrencesElement++;
			}
		}
	}
	public void cleanValidator() {
		this.collectionValidator.getElement().clear();
	}
	private boolean isNamespaceInList(List<String> namespacesList, String ns) {
		
		for (String nsInList : namespacesList) {
			if (nsInList.equalsIgnoreCase(ns))
				return true;
		}
		return false;
	}
	
	public List<String> addNamespaceToList(List<String> namespacesList, String ns) {
		List<String> auxList = namespacesList;
		if (!isNamespaceInList(namespacesList, ns))
			auxList.add(ns);
		return auxList;
	}
}
